package io.github.frailty9.crserver.dso.service.impl;

import com.mybatisflex.kotlin.extensions.db.baseMapper
import com.mybatisflex.kotlin.extensions.db.insert
import com.mybatisflex.kotlin.extensions.db.query
import com.mybatisflex.kotlin.extensions.db.queryAs
import com.mybatisflex.kotlin.extensions.db.queryOne
import com.mybatisflex.kotlin.extensions.db.update
import com.mybatisflex.kotlin.extensions.kproperty.eq
import com.mybatisflex.kotlin.extensions.wrapper.whereWith
import io.github.frailty9.crserver.dso.mapper.ReservationMapper
import io.github.frailty9.crserver.model.Reservation;
import io.github.frailty9.crserver.dso.service.ReservationService;
import io.github.frailty9.crserver.exception.DataInconsistencyException
import io.github.frailty9.crserver.model.Resource
import io.github.frailty9.crserver.model.ResourceInstance
import io.github.frailty9.crserver.utils.DateRange
import mu.KotlinLogging
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject
import org.noear.solon.data.annotation.Transaction
import org.noear.solon.scheduling.annotation.Scheduled
import java.lang.IllegalArgumentException
import java.time.LocalDateTime

/**
 * ReservationServiceImpl
 */
@Component
open class ReservationServiceImpl : ReservationService {

    val logger = KotlinLogging.logger { }

    @Inject
    lateinit var reservationMapper: ReservationMapper

    /**
     * 每5分钟调用一次过期检查
     */
    @Transaction
    @Scheduled(cron = "0 */5 * * * ?")
    fun checkAndUpdateReservationStatus() {
        reservationMapper.updateExpired()
    }

    @Transaction
    override fun booking(uid: Long, resourceInstanceId: Long, bookUnit: Int): Long {
        // 校验资源实例status
        val instance = queryOne<ResourceInstance> { ResourceInstance::id eq resourceInstanceId }!!

        if (instance.status != 1 || instance.remainingUnits!! <= 0) return 0

        // 校验用户是否达到周期内预约上限
        if (!isUserCanBook(uid, resourceInstanceId)) return 0

        // 扣余量
        val remainingUnits = instance.remainingUnits!! - 1
        instance.remainingUnits = remainingUnits
        if (remainingUnits <= 0) instance.status = 0

        logger.debug(instance.toString())
//        update<ResourceInstance> {
//            ResourceInstance::remainingUnits set instance.remainingUnits
//            ResourceInstance::status set instance.status
//            whereWith {
//                ResourceInstance::id eq instance.id
//            }
//        }
        ResourceInstance::class.baseMapper.update(instance, true)

        // 预约资源
        val reservation = Reservation(
            userId = uid,
            resourceId = instance.resourceId,
            instanceId = resourceInstanceId,
            unitsBooked = bookUnit,
            status = 1,
        )
        insert(reservation)
        return reservation.id!!
    }

    @Transaction(readOnly = true)
    override fun getById(id: Long): Reservation? {
        val reservation = queryOne<Reservation> {
            whereWith {
                Reservation::id eq id
            }
        } ?: return null
        val instance = queryOne<ResourceInstance> {
            select(ResourceInstance::endTime)
            ResourceInstance::id eq reservation.instanceId
        } ?: throw DataInconsistencyException("异常的resourceInstance")
        if (instance.endTime!!.isBefore(LocalDateTime.now()))
            reservation.status = 3
        // 更新状态
        Reservation::class.baseMapper.update(
            Reservation(
                id = reservation.id,
                status = reservation.status,
            ), true
        )
        return reservation
    }

    @Transaction
    override fun cancel(reservationId: Long): Boolean {
        val reservation = queryOne<Reservation> {
            select(
                Reservation::id,
                Reservation::instanceId,
                Reservation::unitsBooked,
                Reservation::status,
                Reservation::checkInTime,
            )
            whereWith {
                Reservation::id eq reservationId
            }
        } ?: return false

        val instance = queryOne<ResourceInstance> {
            select(
                ResourceInstance::id,
                ResourceInstance::startTime,
                ResourceInstance::remainingUnits,
                ResourceInstance::status,
            )
            whereWith {
                ResourceInstance::id eq reservation.instanceId
            }
        } ?: return false

        // 活动开始不可取消
        if (instance.startTime!!.isBefore(LocalDateTime.now())) return false

        // 状态不可取消
        if (reservation.status != 1) return false

        // 加余量
        if (instance.status == 0) instance.status = 1
        instance.remainingUnits = instance.remainingUnits!! + reservation.unitsBooked!!

        // 该预约订单状态
        reservation.status = 0

        // 更新
        Reservation::class.baseMapper.update(reservation, true)
        ResourceInstance::class.baseMapper.update(instance, true)

        return true
    }

    @Transaction(readOnly = true)
    override fun getsByUserId(uid: Long): List<Reservation> {
        return query<Reservation> {
            whereWith {
                Reservation::userId eq uid
            }
        }
    }

    /**
     * 判断用户在对应的周期内是否有剩余名额
     *
     * Issue: 通过为reservation表添加一个派生字段"cycle_key", 简化计算逻辑, 提升性能
     *
     * @param uid 用户id
     * @param instanceId 资源id
     * @return 有/没有
     */
    @Transaction(readOnly = true)
    private fun isUserCanBook(uid: Long, instanceId: Long): Boolean {
        // 查实例表, 取资源id, 开始时间
        val instance = queryOne<ResourceInstance> {
            select(ResourceInstance::resourceId, ResourceInstance::startTime)
            eq(ResourceInstance::id, instanceId)
        } ?: throw IllegalArgumentException("不存在的资源实例|id=${instanceId}")
        // 查记录表, 取用户预约的所有实例id
        val instanceIds = queryAs<Reservation, Long> {
            select(Reservation::instanceId)
            whereWith {
                Reservation::userId eq uid
                Reservation::resourceId eq instance.resourceId
            }
        }
        // 如果没有记录, 直接返回true
        if (instanceIds.isEmpty()) return true
        // 查资源表, 取用户最大可预约量, 周期类型
        val resource = queryOne<Resource> {
            select(Resource::maxBookingsPerCycle, Resource::repeatType)
                .eq(Resource::id, instance.resourceId)
        }
        // 计算周期窗口
        val startTime = instance.startTime?.toLocalDate() ?: throw DataInconsistencyException("异常的startTime")
        val cycleDateRange: DateRange? = when (resource?.repeatType) {
            "DAILY" -> DateRange.ofDaily(startTime)
            "WEEKLY" -> DateRange.ofWeekly(startTime)
            "MONTHLY" -> DateRange.ofMonthly(startTime)
            "ONCE" -> null
            else -> throw DataInconsistencyException("异常的计划类型")
        }
        // 计算用户预约量
        val userReservationCount = query<ResourceInstance> {
            select("1")
            `in`(ResourceInstance::id, instanceIds)
            // 加入周期窗口条件
            if (null != cycleDateRange) {
                ge(ResourceInstance::startTime, cycleDateRange.start.atTime(0, 0))
                le(ResourceInstance::startTime, cycleDateRange.end.atTime(23, 59))
                ne(ResourceInstance::status, 0)
            }
        }.size

        // 校验用户是否达到周期内预约上限
        val maxBookingsPerCycle = resource.maxBookingsPerCycle
            ?: throw NullPointerException("resource.maxBookingsPerCycle | id=${resource.id}")
        return userReservationCount < maxBookingsPerCycle
    }
}
